package com.authine.cloudpivot.web.api.controller.login;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.StrUtil;
import com.authine.cloudpivot.engine.api.model.organization.UserModel;
import com.authine.cloudpivot.engine.api.model.security.ClientModel;
import com.authine.cloudpivot.web.api.dubbo.DubboConfigService;
import com.authine.cloudpivot.web.api.exception.ResultEnum;
import com.authine.cloudpivot.web.api.handler.CustomizedOrigin;
import com.authine.cloudpivot.web.api.service.LdapService;
import com.authine.cloudpivot.web.api.utils.RSAUtils;
import com.authine.cloudpivot.web.sso.handler.DingTalkAjaxAuthenticationHandler;
import com.google.common.collect.Maps;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.client.CookieStore;
import org.apache.http.client.protocol.HttpClientContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLDecoder;
import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * 登录接口
 */
@Api(tags = "登录::认证授权")
@RestController
@Validated
@Slf4j
@RequestMapping("/login/Authentication")
@CustomizedOrigin(level = 0)
public class LoginController {

    private static final String REDIS_LOGIN_FAILED_KEY = "login_failed_count";
    private static final int LOGIN_FAILED_COUNT = 3;

    @Autowired
    private RedisTemplate redisTemplate;

    @Autowired
    private DubboConfigService dubboConfigService;

    @Value("${ldapOne.noAuthUser}")
    private String noAuthUser;

    @Value("${ldapOne.takeEffect}")
    private Boolean takeEffect;

    @Autowired
    private LdapService ldapService;

    @ApiOperation(value = "获取OAuth Code", notes = "获取授权码")
    @PostMapping("/get_code")
//	@ApiImplicitParams({
//			@ApiImplicitParam(name = "url", value = "获取授权码的请求url", defaultValue = "http://ip地址或域名/api/login?redirect_uri=http://ip地址或域名/api/oauth/authorize?client_id=api&response_type=code&scope=read&redirect_uri=http://ip地址或域名/oauth", required = true, dataType = "String", paramType = "path"),
//			@ApiImplicitParam(name = "userName", value = "用户账号", required = true, dataType = "String", paramType = "path"),
//			@ApiImplicitParam(name = "password", value = "登录密码", required = true, dataType = "String", paramType = "path")
//	})
    @ApiImplicitParam(name = "params", value = "{\"username\":\"用户账号\",\"password\":\"登录密码\",\"url\":获取授权码的请求url}", required = true, dataType = "Map")
    @ResponseBody
    public Map<String, Object> getCode(@RequestBody Map<String, String> params, HttpServletRequest request, HttpServletResponse response) {
        // url = "http://ip地址或域名/api/login?redirect_uri=http://ip地址或域名/api/oauth/authorize?client_id=api&response_type=code&scope=read&redirect_uri=http://ip地址或域名/oauth"
        // url = "http://ip地址或域名:8080/login?redirect_uri=http://ip地址或域名:8080/oauth/authorize?response_type=code&client_id=api&scope=read&redirect_uri=http://ip地址或域名/oauth"
        String username = params.get("username");
        String password = params.get("password");
        String url = params.get("url");
        String index = params.get("index");
        String corpId = params.get("corpId");
        String key = params.get("key");

        Map<String, String> map = new HashMap<>();
        Integer count = (Integer) redisTemplate.opsForValue().get(REDIS_LOGIN_FAILED_KEY + username);

        if (count != null && count >= LOGIN_FAILED_COUNT) {
            log.debug("失败{}次，请一分钟后重试", LOGIN_FAILED_COUNT);
            Map<String, Object> mapTemplate = new HashMap<>();
            mapTemplate.put("errcode", 10001);
            mapTemplate.put("errmsg", "失败三次，请一分钟后再试");
            return mapTemplate;
        }

        String passwordTrue = "";
        if (StringUtils.isNotEmpty(index)) {
            String privateKey = (String) redisTemplate.opsForValue().get(index);
            if (StringUtils.isEmpty(privateKey)) {
                log.debug("rsa key不存在");
                Map<String, Object> mapTemplate = new HashMap<>();
                mapTemplate.put("errcode", 10001);
                mapTemplate.put("errmsg", "index缺失，请重新登录");
                return mapTemplate;
            }
            try {
                //获取私钥解密，获取明文密码
                log.info("privateKey:{}", privateKey);
                passwordTrue = RSAUtils.decryptByPrivateKey(password, privateKey);
                log.info("解密后的明文密码:{}", passwordTrue);
            } catch (Exception e) {
                e.printStackTrace();
                Map<String, Object> mapTemplate = new HashMap<>();
                mapTemplate.put("errcode", 100011);
                mapTemplate.put("errmsg", "decryptByPrivateKey算法失效");
                return mapTemplate;
            }
        }

        //判断登录用户是否超级管理员
        if (!"admin".equals(username)) {
            UserModel byUserNameAndCorpId = dubboConfigService.getOrganizationFacade().getUserByUserNameAndCorpId(username, corpId);
            UserModel byMobileAndCorpId = dubboConfigService.getOrganizationFacade().getUserByMobileAndCorpId(username, corpId);
            if (Objects.isNull(byUserNameAndCorpId) && Objects.isNull(byMobileAndCorpId)) {
                log.debug("当前用户不在组织内或者用户不存在");
                Map<String, Object> mapTemplate = new HashMap<>();
                mapTemplate.put("errcode", 1001);
                mapTemplate.put("errmsg", "当前用户不在组织内或者用户不存在");
                return mapTemplate;
            }
        }

        //域登录校验
        boolean flag = false;
        log.info("!noAuthUser.contains(username)结果:{}", !noAuthUser.contains(username));
        if (takeEffect && !noAuthUser.contains(username)) {
            //使用明文密码校验域账号
            flag = ldapService.ldapAuth(username, passwordTrue);
            log.info("==ldapService===校验结果==" + flag);
            if (!flag) {
                Map<String, Object> mapTemplate = new HashMap<>();
                mapTemplate.put("errcode", 1001);
                mapTemplate.put("errmsg", "域账号密码校验错误");
                return mapTemplate;
            }
            //到这里，域登录校验已通过或为域登录白名单，使用入参key，加密默认密码123456
            try {
                //获取私钥，解密
                password = RSAUtils.encryptByPublicKey("123456", key);
                log.info("加密后的password:{}", password);
            } catch (Exception e) {
                e.printStackTrace();
                Map<String, Object> mapTemplate = new HashMap<>();
                mapTemplate.put("errcode", 100011);
                mapTemplate.put("errmsg", "加密算法失效");
                return mapTemplate;
            }
        }


        map.put("username", username);
        map.put("password", password);
        map.put("index", index);
        map.put("corpId", corpId);
        map.put("portal", "true");
        map.put("User-Agent", request.getHeader("User-Agent"));
        map.put("clientIp", request.getRemoteAddr());
        try {
            url = URLDecoder.decode(url, "utf-8");
            HttpClientContext context = HttpClientContext.create();
            Map<String, Object> result = HttpClientHelper.getAouthationPost(url, map, context);

            //将S2S请求的cookies 传递给前端, 用于Session管理
            copyCookies(context, response);

            return result;
        } catch (Exception e) {
            log.error("get oauth code error.", e);
        }
        return null;
    }


    @ApiOperation(value = "刷新Token", notes = "刷新Token")
    @GetMapping("/get_refresh_token")
    @ApiImplicitParams({
            @ApiImplicitParam(name = "url", value = "用于刷新token的url", defaultValue = "http://ip地址或域名/api", required = true, dataType = "String", paramType = "path"),
            @ApiImplicitParam(name = "client_secret", value = "客户端密码", defaultValue = "c31b32364ce19ca8fcd150a417ecce58", required = true, dataType = "String", paramType = "path"),
            @ApiImplicitParam(name = "client_id", value = "客户端id", defaultValue = "api", required = true, dataType = "String", paramType = "path"),
            @ApiImplicitParam(name = "refresh_token", value = "用于刷新token，获取token的时候可以拿到", required = true, dataType = "String", paramType = "path")
    })
    @ResponseBody
    public Map refreshToken(String url, String client_secret, String client_id, String refresh_token) {
        // url = ApiHost, 如 "http://ip地址或域名/api/
        Map<String, String> map = new HashMap<>();
        map.put("url", url);
        map.put("client_secret", client_secret);
        map.put("refresh_token", refresh_token);
        map.put("client_id", client_id);
        map.put("grant_type", "refresh_token");
        return HttpClientHelper.getOauthToken(map);
    }

    @ApiOperation(value = "获取Token")
    @GetMapping("/get_token")
    @ApiImplicitParams({
            @ApiImplicitParam(name = "url", value = "用于刷新token的url", defaultValue = "http://ip地址或域名/api", required = true, dataType = "String", paramType = "path"),
            @ApiImplicitParam(name = "code", value = "授权码", required = true, dataType = "String", paramType = "path"),
            @ApiImplicitParam(name = "client_secret", value = "客户端密码", defaultValue = "c31b32364ce19ca8fcd150a417ecce58", required = true, dataType = "String", paramType = "path"),
            @ApiImplicitParam(name = "client_id", value = "客户端id", defaultValue = "api", required = true, dataType = "String", paramType = "path"),
            @ApiImplicitParam(name = "redirect_uri", value = "重定向URI", defaultValue = "http://ip地址或域名/oauth", required = true, dataType = "String", paramType = "path")
    })
    @ResponseBody
    public Map getToken(String url, String code, String client_secret, String client_id, String redirect_uri) {
        // url = "http://ip地址或域名/api"
        // redirect_uri = "http://ip地址或域名/oauth"

        ClientModel clientModel = dubboConfigService.getSystemSecurityFacade().getClientById(client_id);
        if (clientModel == null) {
            return null;
        }
        String registeredRedirectUris = clientModel.getRegisteredRedirectUris();
        if (StringUtils.isNotEmpty(registeredRedirectUris)) {
            try {
                boolean find = false;
                URL redirectUrl = new URL(url);
                String[] strings = registeredRedirectUris.split(",");
                for (String string : strings) {
                    URL u = new URL(string);
                    if (redirectUrl.getHost().equals(u.getHost())) {
                        find = true;
                    }
                }
                if (!find) {
                    return null;
                }
            } catch (MalformedURLException e) {
                log.error(e.getMessage(), e);
                return null;
            }
        }
        Map<String, String> map = new HashMap<>();
        map.put("url", url);
        map.put("client_secret", client_secret);
        map.put("redirect_uri", redirect_uri);
        map.put("client_id", client_id);
        map.put("grant_type", "authorization_code");
        map.put("code", code);

        Map result = HttpClientHelper.getOauthToken(map);
        if (result != null) {
            result.put("success", true);
            try {
                if (result.get("access_token") != null && result.get("refresh_token") != null && result.get("exp") != null) {
                    String exp = String.valueOf(result.get("exp"));
                    exp = exp.length() == 10 ? exp : exp.substring(0, 10);
                    long betweenExpire = DateUtil.betweenMs(new Date(), new Date((Long.parseLong(exp) * 1000)));
                    redisTemplate.opsForValue().set(DingTalkAjaxAuthenticationHandler.REDIS_LOGIN_AUTH_SUCCESS_USER + result.get("access_token"), result.get("refresh_token") + StrUtil.COMMA + result.get("exp") + "000", betweenExpire, TimeUnit.MILLISECONDS);
                }
            } catch (Exception ex) {
                log.error("解析异常登录信息：", ex);
            }
        }
        return result;
    }

    @ApiOperation(value = "根据access_token获取refresh_token")
    @PostMapping("/get_refresh_token")
    @ApiImplicitParams({
            @ApiImplicitParam(name = "access_token", value = "用于获取refresh_token", required = true, dataType = "String", paramType = "path")
    })
    @ResponseBody
    public Map getRefreshToken(@RequestBody Map<String, String> params) {
        Map<String, Object> map = Maps.newHashMap();
        Map<String, String> data = null;
        Long errcode = ResultEnum.SUCCESS.getErrCode();
        String errmsg = ResultEnum.SUCCESS.getErrMsg();
        String refresh = (String) redisTemplate.opsForValue().get(DingTalkAjaxAuthenticationHandler.REDIS_LOGIN_AUTH_SUCCESS_USER + params.get("access_token"));
        if (StringUtils.isNotBlank(refresh)) {
            String[] refreshArr = refresh.split(StrUtil.COMMA);
            if (refreshArr.length == 2) {
                data = Maps.newHashMap();
                data.put("refresh_token", refreshArr[0]);
                String expirationStr = refreshArr[1];
                expirationStr = (StringUtils.isNotEmpty(expirationStr) && expirationStr.length() == 13) ? expirationStr.substring(0, expirationStr.length() - 3) : expirationStr;
                data.put("expiration", expirationStr);
            } else {
                errcode = ResultEnum.JSON_PASE_ERR.getErrCode();
                errmsg = ResultEnum.JSON_PASE_ERR.getErrMsg();
            }
        } else {
            errcode = ResultEnum.ACCESS_TOKEN_INVALID.getErrCode();
            errmsg = ResultEnum.ACCESS_TOKEN_INVALID.getErrMsg();
        }
        map.put("data", data);
        map.put("errcode", errcode);
        map.put("errmsg", errmsg);
        return map;
    }

    private void copyCookies(HttpClientContext context, HttpServletResponse response) {
        CookieStore cookieStore = context.getCookieStore();
        if (cookieStore != null) {
            List<org.apache.http.cookie.Cookie> cookies = cookieStore.getCookies();
            cookies.forEach(cookie -> {
                Cookie c = aCookieToSCookie(cookie);
                //add cookie to response
                response.addCookie(c);
            });
        }
    }

    private Cookie aCookieToSCookie(org.apache.http.cookie.Cookie cookie) {
        Cookie c = new Cookie(cookie.getName(), cookie.getValue());
        c.setComment(cookie.getComment());
        c.setDomain(cookie.getDomain());
        //c.setHttpOnly(cookie.getXxx);
        c.setSecure(cookie.isSecure());
        c.setPath(cookie.getPath());
        Date expiryDate = cookie.getExpiryDate();
        if (expiryDate != null) {
            int t = (int) ((expiryDate.getTime() - System.currentTimeMillis()) / 1000);
            c.setMaxAge(t);
        }
        c.setVersion(cookie.getVersion());
        return c;
    }
}
